use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
use crate::state::{read_config, store_config, ConfigInfo};
use cosmwasm_std::{
    log, to_binary, Api, BankMsg, Binary, Coin, CosmosMsg, Env, Extern, HandleResponse,
    HandleResult, HumanAddr, InitResponse, Querier, StdError, StdResult, Storage, Uint128,
};
use hermit_toolkit_hmip21::transfer_msg;
use std::f32::consts::E;
use std::ops::Add;

const FEE_DECIMAL: u64 = 10000;
const BLOCK_SIZE: usize = 256;

// Note, you can use StdResult in some functions where you do not make use of the custom errors
pub fn init<S: Storage, A: Api, Q: Querier>(
    deps: &mut Extern<S, A, Q>,
    env: Env,
    msg: InstantiateMsg,
) -> StdResult<InitResponse> {
    if msg.bridge_fee > FEE_DECIMAL {
        return Err(StdError::generic_err("bridge_fee error"));
    }
    // let owner = msg.owner.unwrap_or(env.message.sender);
    let config = ConfigInfo {
        owner: msg.owner,
        usdt_token_address: msg.usdt_token_address,
        usdt_token_code_hash: msg.usdt_token_code_hash,
        swaper_address: msg.swaper_address,
        swaper_code_hash: msg.swaper_code_hash,
        bridge_fee: msg.bridge_fee,
        min_bridge_fee: msg.min_bridge_fee,
        exec_bot: msg.exec_bot,
        per_tx_limit: msg.per_tx_limit,
        daily_tx_limit: msg.daily_tx_limit,
        daily_exec_amount: Uint128::zero(),
        update_block_time: env.block.time,
    };

    store_config(&mut deps.storage, &config)?;

    Ok(InitResponse {
        messages: vec![],
        log: vec![log("status", "success")], // See https://github.com/CosmWasm/wasmd/pull/386
    })
}

// And declare a custom Error variant for the ones where you will want to make use of it
pub fn handle<S: Storage, A: Api, Q: Querier>(
    deps: &mut Extern<S, A, Q>,
    env: Env,
    msg: ExecuteMsg,
) -> HandleResult {
    match msg {
        ExecuteMsg::UpdateConfig { config } => execute_update_config(deps, env, config),
        ExecuteMsg::ExecuteTask {
            recipient,
            swap_gas_fee,
            amount_out,
        } => execute_task(deps, env, recipient, swap_gas_fee, amount_out),
        ExecuteMsg::Withdraw { amount } => withdraw(deps, env, amount),
    }
}

pub fn execute_task<S: Storage, A: Api, Q: Querier>(
    deps: &mut Extern<S, A, Q>,
    env: Env,
    recipient: HumanAddr,
    swap_gas_fee: Uint128,
    amount_out: Uint128,
) -> HandleResult {
    // update exec amount
    let mut config_s = read_config(&deps.storage)?;
    if env.block.time - config_s.update_block_time > 86400 {
        config_s.daily_exec_amount = Uint128::zero();
        config_s.update_block_time = env.block.time;
    } else {
        config_s.daily_exec_amount = config_s.daily_exec_amount.add(amount_out);
    }
    store_config(&mut deps.storage, &config_s)?;
    // load config info
    let config = read_config(&deps.storage)?;
    // authorize exec bot
    if env.message.sender != config.exec_bot {
        return Err(StdError::generic_err("Err Auth"));
    }
    // check per_tx_limit
    if amount_out > config.per_tx_limit {
        return Err(StdError::generic_err("Over per tx limit"));
    }
    // check daily_tx_limit
    if config.daily_exec_amount.add(amount_out) > config.daily_tx_limit {
        return Err(StdError::generic_err("Over daily tx limit"));
    }
    // let mut bridge_fee = amount_out.multiply_ratio(config.bridge_fee, FEE_DECIMAL);
    // if bridge_fee < config.min_bridge_fee {
    //     bridge_fee = config.min_bridge_fee;
    // }
    // let mut amount_out = amount_out.sub(bridge_fee)?;

    if swap_gas_fee > amount_out {
        return Err(StdError::generic_err("swap_gas_fee error"));
    }
    // amount_out = amount_out.sub(swap_gas_fee)?;

    // Generate hmip20 transfer messages if necessary
    let mut transfer_msgs: Vec<CosmosMsg> = vec![];
    transfer_msgs.push(transfer_msg(
        recipient.clone(),
        amount_out,
        None,
        None,
        BLOCK_SIZE,
        config.usdt_token_code_hash,
        config.usdt_token_address,
    )?);
    if swap_gas_fee > Uint128::zero() {
        transfer_msgs.push(CosmosMsg::Bank(BankMsg::Send {
            from_address: env.contract.address.clone(),
            to_address: recipient.clone(),
            amount: vec![Coin::new(
                swap_gas_fee.multiply_ratio(1u64, 100u64).u128(),
                "ughm",
            )],
        }));
    }

    Ok(HandleResponse {
        messages: transfer_msgs,
        log: vec![
            log("action", "execute_task"),
            log(
                "assets",
                format!(
                    "recipient: {}, swap_gas_fee: {}, amount_out: {}",
                    recipient, swap_gas_fee, amount_out
                ),
            ),
        ],
        data: None,
    })
}

pub fn execute_update_config<S: Storage, A: Api, Q: Querier>(
    deps: &mut Extern<S, A, Q>,
    env: Env,
    config: ConfigInfo,
) -> HandleResult {
    // load config info
    let cur_config = read_config(&deps.storage)?;
    // authorize owner
    if env.message.sender != cur_config.owner {
        return Err(StdError::unauthorized());
    }

    store_config(&mut deps.storage, &config)?;

    Ok(HandleResponse {
        messages: vec![],
        log: vec![log("action", "update_config")],
        data: None,
    })
}

pub fn withdraw<S: Storage, A: Api, Q: Querier>(
    deps: &mut Extern<S, A, Q>,
    env: Env,
    amount: Uint128,
) -> StdResult<HandleResponse> {
    let config = read_config(&deps.storage)?;
    // authorize owner
    if env.message.sender != config.owner {
        return Err(StdError::unauthorized());
    }
    let mut transfer_msgs: Vec<CosmosMsg> = vec![];
    transfer_msgs.push(transfer_msg(
        env.message.sender,
        amount,
        None,
        None,
        BLOCK_SIZE,
        config.usdt_token_code_hash,
        config.usdt_token_address,
    )?);

    Ok(HandleResponse {
        messages: transfer_msgs,
        log: vec![log("action", "withdraw"), log("amount", amount)],
        data: None,
    })
}

pub fn query<S: Storage, A: Api, Q: Querier>(
    deps: &Extern<S, A, Q>,
    msg: QueryMsg,
) -> StdResult<Binary> {
    match msg {
        QueryMsg::Config {} => to_binary(&query_config(deps)?),
    }
}

pub fn query_config<S: Storage, A: Api, Q: Querier>(
    deps: &Extern<S, A, Q>,
) -> StdResult<ConfigInfo> {
    // load config info
    let config = read_config(&deps.storage)?;

    Ok(config)
}
